/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2009 - 2012 Luca Mingardi.
 *
 * This file is part of jeeObserver.
 *
 * JeeObserver is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * JeeObserver is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package jeeobserver;

import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.Date;
import java.util.logging.Level;

/**
 * The Class ActionObserver.
 *
 * @author Luca Mingardi
 * @version 4.0
 */
public class ActionObserver extends Observer implements Serializable {

	 private static final long serialVersionUID = 40L;

	 /** The Constant ACTION_CATEGORY_JSF. */
	 public static final int CATEGORY_JSF = 1;

	 /** The Constant ACTION_CATEGORY_EJB. */
	 public static final int CATEGORY_EJB = 2;

	 /** The Constant ACTION_CATEGORY_SERVLET. */
	 public static final int CATEGORY_SERVLET = 3;

	 /** The Constant ACTION_CATEGORY_JDBC. */
	 public static final int CATEGORY_JDBC = 4;

	 /** The Constant ACTION_CATEGORY_CUSTOM. */
	 public static final int CATEGORY_CUSTOM = 5;

	 private final String project;

	 private final int category;

	 private final String name;

	 private final String principal;

	 private Date date;

	 private Exception exception;

	 private long cpuNanos;

	 private long timeNanos;

	 private transient long startTimeNanos;

	 private transient long stopTimeNanos;

	 private transient long startThreadNanos;

	 private transient long stopThreadNanos;

	 private final transient ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

	 //Test
	 ActionObserver(Date date, String context, String project, int category, String name, long cpuNanos, long timeNanos, String principal, Exception exception) {
			super(context);
			this.project = project;
			this.category = category;
			this.name = name;
			this.principal = principal;
			this.date = date;

			this.cpuNanos = cpuNanos;
			this.timeNanos = timeNanos;

			this.exception = exception;
	 }

	 public ActionObserver(String project, String name) {
			this(ActionObserver.CATEGORY_CUSTOM, project, name, null);
	 }

	 public ActionObserver(String project, String name, String principal) {
			this(ActionObserver.CATEGORY_CUSTOM, project, name, principal);
	 }

	 ActionObserver(int category, String project, String name, String principal) {

			super(JeeObserverContext.getInstance().getContext());

			this.category = category;

			this.project = (project == null) ? "" : project;

			this.name = (name == null) ? "" : name;

			this.principal = principal;
	 }

	 public void start() {

			if (JeeObserverContext.getInstance() != null) {

				 this.setRunning(true);

				 // The order of these two calls matters: before system, after thread
				 this.date = new Date(System.currentTimeMillis());

				 this.startTimeNanos = System.nanoTime();

				 if (!this.threadMXBean.isCurrentThreadCpuTimeSupported()) {
						JeeObserverContext.logger.log(Level.WARNING, "CPU Usage monitoring is not avaliable.");
						this.startThreadNanos = 0;
				 } else {
						this.threadMXBean.setThreadCpuTimeEnabled(true);
						this.startThreadNanos = this.threadMXBean.getCurrentThreadCpuTime();
				 }

			} else {
				 JeeObserverContext.logger.log(Level.FINE, "JeeObserver context not exists.");
			}
	 }

	 public void stop() {

			if (JeeObserverContext.getInstance() != null) {
				 if (this.isRunning()) {

						// The order of these two calls matters: before thread, after system
						if (!this.threadMXBean.isCurrentThreadCpuTimeSupported()) {
							 JeeObserverContext.logger.log(Level.WARNING, "CPU Usage monitoring is not avaliable.");
							 this.stopThreadNanos = 0;
						} else {
							 this.threadMXBean.setThreadCpuTimeEnabled(true);
							 this.stopThreadNanos = this.threadMXBean.getCurrentThreadCpuTime();

							 if ((this.startThreadNanos == 0) || (this.startThreadNanos > this.stopThreadNanos)) {
									this.startThreadNanos = this.stopThreadNanos;
							 }
						}
						this.stopTimeNanos = System.nanoTime();

						if ((this.startTimeNanos == 0) || (this.startTimeNanos > this.stopTimeNanos)) {
							 this.startTimeNanos = this.stopTimeNanos;
						}


						//Calculate totals
						this.timeNanos = this.stopTimeNanos - this.startTimeNanos;

						this.cpuNanos = this.stopThreadNanos - this.startThreadNanos;

						// Write log entry
						JeeObserverContext.getInstance().addObserver(this);

				 }
			} else {
				 JeeObserverContext.logger.log(Level.FINE, "JeeObserver context not exists.");
			}

			this.setRunning(false);
	 }

	 void throwException(Throwable exception) {
			if (this.isRunning()) {
				 this.exception = new Exception(exception);

				 this.stop();
			}
	 }

	 public String getProject() {
			return this.project;
	 }

	 public int getCategory() {
			return this.category;
	 }

	 public String getPrincipal() {
			return this.principal;
	 }

	 public String getName() {
			return this.name;
	 }

	 public Date getDate() {
			return date;
	 }

	 public long getCpuNanos() {
			return cpuNanos;
	 }

	 public long getTimeNanos() {
			return timeNanos;
	 }

	 public Exception getException() {
			return this.exception;
	 }

	 public static final class Exception implements Serializable {

			private static final long serialVersionUID = 40L;

			private final String type;

			private final String message;

			private final String stackTrace;

			Exception(Throwable exception) {

				 this.type = exception.getClass().getName();

				 this.message = exception.getMessage();

				 final StringWriter stringWriter = new StringWriter();
				 final PrintWriter printWriter = new PrintWriter(stringWriter, true);
				 exception.printStackTrace(printWriter);
				 printWriter.flush();
				 stringWriter.flush();
				 this.stackTrace = stringWriter.toString();

			}

			/**
			 * Gets the class name.
			 *
			 * @return the class name
			 */
			public String getType() {
				 return this.type;
			}

			/**
			 * Gets the message.
			 *
			 * @return the message
			 */
			public String getMessage() {
				 return this.message;
			}

			/**
			 * Gets the stack trace.
			 *
			 * @return the stack trace
			 */
			public String getStackTrace() {
				 return this.stackTrace;
			}
	 }
}